package jp.syuriken.snsw.terenaibot;

import java.io.File;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.LinkedList;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.logging.Level;

/**
 * 変更されるようなデータを読み書きするための{@link DataProvider}クラスを利用するための、
 * 同じデータファイルに対してシングルインスタンスを提供するクラスです。
 * 
 * @author $Author: snswinhaiku.lo@gmail.com $
 */
public class DataProviderFactory {
	
	/**
	 * このクラスは同期化されません。
	 * 
	 * @author $Author: snswinhaiku.lo@gmail.com $
	 */
	private static class DataProviderHoldingClass {
		
		private TreeMap<String, WeakReference<DataProvider>> map;
		
		private LinkedList<DataProvider> recentQueue;
		
		private int capacity;
		
		private BotConfiguration botConfiguration;
		

		/**
		 * インスタンスを生成する。
		 * @param capacity 容量
		 * @param botConfiguration ボットの設定
		 */
		public DataProviderHoldingClass(int capacity, BotConfiguration botConfiguration) {
			map = new TreeMap<String, WeakReference<DataProvider>>();
			if (capacity < 1) {
				throw new RuntimeException("capacityには1以上の整数を指定してください");
			}
			this.capacity = capacity;
			this.botConfiguration = botConfiguration;
			recentQueue = new LinkedList<DataProvider>();
		}
		
		public DataProvider get(String dataFile) {
			WeakReference<DataProvider> reference = map.get(dataFile);
			if (reference == null) {
				return null;
			}
			DataProvider dataProvider = reference.get();
			if (dataProvider == null) {
				map.remove(dataFile);
			}
			return dataProvider;
		}
		
		public void put(String dataFile, DataProvider provider) {
			if (recentQueue.size() > capacity) {
				recentQueue.poll();
			}
			map.put(dataFile, new WeakReference<DataProvider>(provider));
			recentQueue.add(provider);
		}
		
		public void remove(String dataFile) {
			map.remove(dataFile);
		}
		
		/**
		 * データを保存する。
		 */
		public void store() {
			for (Entry<String, WeakReference<DataProvider>> entryRef : map.entrySet()) {
				DataProvider entry = null;
				try {
					entry = entryRef.getValue().get();
					if (entry != null) {
						entry.store();
					} else {
						map.remove(entryRef.getKey());
					}
				} catch (IOException e) {
					// ↓は、entry!=nullのentry.store()内でのみ例外がスローされるので不要。
					/* if (entry != null) { */
					botConfiguration.getLogger().log(Level.WARNING, e,
							String.format("DataProviderFactory: %sの保管中にエラーが発生しました", entry.getFileName()));
					/* } */
				}
			}
		}
	}
	

	/** デフォルトのインスタンス保持数 */
	protected static final int DEFAULT_PROVIDER_INSTANCE_COUNT = 50;
	
	/** DataProviderインスタンス保持クラス */
	protected DataProviderHoldingClass holdingInstance;
	
//	private BotConfiguration botConfiguration;
	
	/** データを保存するディレクトリ */
	protected String dataDir;
	

	/**
	 * インスタンスを生成する。
	 * 
	 * @param botConfiguration ボットの設定
	 * @param dataDir データを格納するディレクトリ
	 * @throws ConfigurationException 設定エラー
	 */
	public DataProviderFactory(BotConfiguration botConfiguration, String dataDir) throws ConfigurationException {
		try {
			holdingInstance =
					new DataProviderHoldingClass(Integer.parseInt(botConfiguration.getResourceBundle().getString(
							"core.dataprovider.instances", String.valueOf(DEFAULT_PROVIDER_INSTANCE_COUNT))),
							botConfiguration);
		} catch (NumberFormatException e) {
			throw new ConfigurationException(botConfiguration.getConfigurationFile(), "core.dataprovider.instances",
					ConfigurationExceptionType.NOT_DECIMAL);
		}
		this.dataDir = dataDir + "/";
//		this.botConfiguration = botConfiguration;
	}
	
	/**
	 * {@link DataProvider}インスタンスを取得する。
	 * このクラスを利用する意味は、ファイルの反復不可能読み込みを防止するためです。
	 * 
	 * @param dataFile データファイル名。ディレクトリ名は普通必要ありません
	 * @return DataProviderインスタンス
	 */
	public DataProvider getProvider(String dataFile) {
		DataProvider dataProvider;
		synchronized (holdingInstance) {
			dataProvider = holdingInstance.get(dataFile);
			if (dataProvider == null) {
				dataProvider = new DataProvider(dataDir + dataFile);
				holdingInstance.put(dataFile, dataProvider);
			}
		}
		return dataProvider;
	}
	
	/**
	 * データファイルを削除する。
	 * 
	 * @param dataFile データファイル名
	 * @return 削除されたかどうか。存在しない場合は常に true を返します。
	 */
	public boolean removeDataFile(String dataFile) {
		boolean isSuccess = false;
		File realDataFile = new File(dataDir + dataFile);
		if (realDataFile.exists()) {
			isSuccess = realDataFile.delete();
		} else {
			isSuccess = true;
		}
		if (isSuccess) {
			synchronized (holdingInstance) {
				holdingInstance.remove(dataFile);
			}
		}
		return isSuccess;
	}
	
	/**
	 * データを保存する。これは保持しているインスタンスしか保存命令を出しません。
	 */
	public void store() {
		synchronized (holdingInstance) {
			holdingInstance.store();
		}
	}
}
